#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <fcntl.h>
#include <unistd.h>

#ifdef USE_CAPSICUM
#include <sys/capsicum.h>
#endif

#include <err.h>
#include <sysexits.h>

#include "au.h"
#include "common.h"

#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef BSIZE
#define BSIZE 0x4000
#endif

static ssize_t	ncpy(int, int, void *, size_t);

int
main(int argc, char *argv[])
{
#ifdef USE_CAPSICUM
	cap_rights_t rights;
#endif
	struct au au = AU_INITIALIZER;
	const char *str;
	void *b;
	ssize_t r;
	size_t bsize = BSIZE;
	size_t i;
	int raw = 0;
	int h = 1;
	int l;
	int c;

	l = snprintf(NULL, 0, "%"PRIu32, UINT32_MAX); /* 10 */
	while ((c = getopt(argc, argv, "b:hr")) != -1)
		switch (c) {
		case 'b':
			if ((bsize = strtosz(optarg)) < l * 5 + 5)
				errx(EX_USAGE,
				    "The buffer size is too small");
			break;
		case 'h':
			h = 0;
			break;
		case 'r':
			raw = 1;
			break;
		default:
			goto usage;
		}
	if (optind != argc)
		goto usage;
	if ((b = malloc(bsize)) == NULL)
		err(EX_OSERR, "Could not allocate the buffer");
#ifdef USE_PLEDGE
	(void)pledge("stdio", NULL);
#endif
#ifdef USE_CAPSICUM
	(void)cap_rights_init(&rights, CAP_READ);
	if (cap_rights_limit(STDIN_FILENO, &rights) == -1 &&
	    errno != ENOSYS)
		err(EX_OSERR, "Could not limit the standard input");
	(void)cap_rights_init(&rights, CAP_WRITE);
	if (cap_rights_limit(STDOUT_FILENO, &rights) == -1 &&
	    errno != ENOSYS)
		err(EX_OSERR, "Could not limit the standard output");
	if (cap_rights_limit(STDERR_FILENO, &rights) == -1 &&
	    errno != ENOSYS)
		err(EX_OSERR, "Could not limit the standard error");
#endif
	while (nread(STDIN_FILENO, b, AU_SIZE) == AU_SIZE) {
		if (au_gethdr(&au, b) != 0)
			errx(EX_DATAERR, "Bad header");
		if (nwrite(STDOUT_FILENO, b, AU_SIZE) != AU_SIZE)
			err(EX_IOERR,
			    "Could not write audio Header");
		if (h) {
			(void)fprintf(stderr, "%*s%*s%*s%*s%*s\n",
			    l, "OFFSET", l + 1, "SIZE",
			    l + 1, "ENCODING", l + 1, "RATE",
			    l + 1, "CHANNELS");
			h = 0;
		}
		i = 0;
		(void)sprintf((char *)b + i, "%*"PRIu32, l, au.au_off);
		i += l;
		if (!raw && au.au_size == UINT32_MAX)
			(void)sprintf((char *)b + i, "%*s", l + 1,
			    "undefined");
		else
			(void)sprintf((char *)b + i, "%*"PRIu32, l + 1,
			    au.au_size);
		i += l + 1;
		if (!raw && (str = au_encstr(au.au_enc)) != NULL)
			(void)sprintf((char *)b + i, "%*s", l + 1, str);
		else
			(void)sprintf((char *)b + i, "%*"PRIu32, l + 1,
			    au.au_enc);
		i += l + 1;
		(void)sprintf((char *)b + i, "%*"PRIu32, l + 1,
		    au.au_rate);
		i += l + 1;
		(void)sprintf((char *)b + i, "%*"PRIu32, l + 1,
		    au.au_chan);
		i += l + 1;
		(void)sprintf((char *)b + i, "\n");
		i += 1;
		if (nwrite(STDERR_FILENO, b, i) != i)
			err(EX_IOERR,
			    "Could not write header info");
		for (i = AU_SIZE; i < au.au_off; i += r)
			if ((r = ncpy(STDIN_FILENO, STDOUT_FILENO, b,
			    MIN(au.au_off - i, bsize))) < 0)
				err(EX_IOERR, "error while copying "
				    "stdin -> stdout");
		while ((r = ncpy(STDIN_FILENO, STDOUT_FILENO, b,
			    MIN(au.au_size, bsize))) > 0)
			if (au.au_size != UINT32_MAX)
				au.au_size -= r;
		if (r < 0)
			err(EX_IOERR, "error while copying "
			    "stdin -> stdout");
	}
	free(b);
	return (0);
usage:
	(void)fprintf(stderr, "usage: auinfo [-hr] [-b buffer-size]\n");
	return (EX_USAGE);
}

ssize_t
ncpy(int in, int out, void *b, size_t s)
{
	ssize_t t;

	if ((t = nread(in, b, s)) > 0)
		t = nwrite(out, b, s);
	return (t);
}
